/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package lime2.ca.plain;
import lime2.ca.EngMsg;
import lime2.*;
import java.io.*;
import java.net.*;
import java.util.*;

/**
 * Advertises this LimeServer and detects the other hosts by means of a beaconing mechanism.
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini 
 */

class PlainDetector implements Runnable
{
 // private data members	
 private boolean detecting;    // activates/deactivates the detector 
 private Hashtable cStatus;    // all engaged hosts: [LimeServerID,Long] where Long is a timestamp
 private Hashtable engaged;    // hosts that engaged during the last time quantum: [LimeServerID,LimeServerID]
 private Hashtable disengaged; // hosts that disengaged during the last time quantum: [LimeServerID,LimeServerID]
 /**
  * Constructor method.
  */
 PlainDetector()
 {
  super();	
  this.detecting = false;
  cStatus = new Hashtable();
  engaged = new Hashtable();
  disengaged = new Hashtable();
	 
 }

 /**
  * Returns whether an host is engaged. Synchronization could be avoided.
  * 
  * @param host
  * @return <code>true</code> or <code>false</code>
  */
 synchronized boolean queryHost(LimeServerID host)
 {
  if (cStatus.get(host) == null) return false; else return true;	
 }
 
 /**
  * Adds/refreshes an engaging/engaged host. Synchronized: the host related data structures must be updated together.
  * 
  * @param host
  */
 synchronized void addHost(LimeServerID host)
 {	
  if (cStatus.get(host) == null) { engaged.put(host, host); disengaged.remove(host); }
  cStatus.put(host, new Long(new Date().getTime()));		
  if (LimeConstants.FORCE_MAX_REFRESH) refreshSystem();
 }
 
 /**
  * Removes a disengaging host. Synchronized: the host related data structures must be updated together.
  * 
  * @param host
  */
 synchronized void removeHost(LimeServerID host)
 {
  if (cStatus.get(host) != null) { engaged.remove(host); disengaged.put(host, host); }
  cStatus.remove(host);
  if (LimeConstants.FORCE_MAX_REFRESH) refreshSystem();
 }
 
 /**
  * Removes hosts that did not advertise themselves for too much time. Synchronized: the host related data structures
  * must be updated together.
  */
 synchronized private void removeDeadHosts()
 {
  long now = new Date().getTime();
  for (Enumeration e = cStatus.keys(); e.hasMoreElements();)
  { 
   LimeServerID key = (LimeServerID) e.nextElement();
   Long entry = (Long) cStatus.get(key); 	
   if (now - entry.longValue() >= LimeConstants.DEAD_HOST_TIMEOUT) removeHost(key);
  } 
 }
 
 /**
  * Updates the LimeSystemTupleSpace, which is an higher layer. Synchronized: the host related data structures must be
  * updated together.
  */
 synchronized private void refreshSystem()
 {
  LimeSystemTupleSpace.update(engaged, disengaged);
  engaged.clear();
  disengaged.clear();
 }
 
 /**
  * Cleans all the host related structures. Synchronized: the host related data structures must be updated together.
  */
 synchronized private void cleanHosts()
 {
  LimeSystemTupleSpace.clear();
  engaged.clear();
  disengaged.clear();
  cStatus.clear();
 }
 
 /**
  * Enables detector.
  */
 void enable()
 {	
  detecting = true;
  new Thread(this).start(); 
 }
 
 /**
  * Disables detector.
  */ 
 void disable()
 {
  detecting = false;	
 }

 /**
  * Collects an engagement/disengagement message.
  * 
  * @param em engagement/disengagement message
  */
 void collectMsg(EngMsg em)
 {
  if (detecting)
  {	
   if (em.type == LimeConstants.HELLO) 
	   { addHost(em.source);  }else removeHost(em.source); 
	   }
  }
 
 
 /**
  * Run method.
  */
 public void run()
 {
  boolean joined = false;
   
  // joins the multicast group
  while (detecting && !joined)
  {	
   try { PlainCA.msock.joinGroup(PlainCA.maddr); joined = true; } 
   catch (IOException ioe) { try { Thread.sleep(LimeConstants.DETECT_PHASE); } catch (InterruptedException ie) {} }
  } 
   
  // advertises this host & removes silent ones
  int n = 0;
  while (detecting) 
  {
   try { PlainCA.ssock.send(new DatagramPacket(EngMsg.HELLO, EngMsg.HELLO.length, PlainCA.maddr, PlainCA.mport));
   } 
   catch (IOException ioe) {}
   
   try { Thread.sleep(LimeConstants.DETECT_PHASE); } 
   catch (InterruptedException ie) {}
  
   removeDeadHosts();
   
   // system update !
   if (!LimeConstants.FORCE_MAX_REFRESH) 
   { 
   	if (n >= LimeConstants.PHASES_N - 1) n = 0; else n++;  
    if (n == 0) refreshSystem(); 
   }                                     
  }
  
  // says goodbye
  try { PlainCA.ssock.send(new DatagramPacket(EngMsg.BYE, EngMsg.BYE.length, PlainCA.maddr, PlainCA.mport)); }
  catch (IOException ioe) {}
  
  // leaves the multicast group
  try { PlainCA.msock.leaveGroup(PlainCA.maddr); } 
  catch (IOException ioe) {}
 
  // resets host related data structures
  cleanHosts();
 }
 
}

 